package mango.orm.connect;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;

import javax.sql.DataSource;

import mango.Mango;
import mango.orm.OrmException;

public class _DataSource implements DataSource {
    private ArrayList<_Connection> conns = new ArrayList<_Connection>();
    private ConnectionParam param;
    private boolean closed = false;

    public _DataSource(ConnectionParam connParam) {
        this.param = connParam;
        DSMonitor monitor = new DSMonitor(this);
        monitor.start();
    }

    public Connection getConnection(String user, String password) throws SQLException {
        if(this.closed) {
            return null;
        }
        Connection conn = this.getFreeConnection(0);
        if (conn == null) {
            if (this.conns.size() >= this.param.getMaxConnection()) {
                conn = this.getFreeConnection(this.param.getLoginTimeout() * 1000);
            }
            else {
                synchronized (this.conns) {
                    Connection pugeConn = DriverManager.getConnection(this.param.getUrl(), user,
                            password);
                    _Connection conn2 = new _Connection(pugeConn, true);
                    conns.add(conn2);
                    conn = conn2.getConnection();
                }
            }
        }
        return conn;
    }

    public Connection getConnection() throws SQLException {
        return this.getConnection(this.param.getUser(), this.param.getPassword());
    }

    private Connection getFreeConnection(long timeout) {
        Connection conn = null;
        synchronized (this.conns) {
            Iterator<_Connection> iter = this.conns.iterator();
            while (iter.hasNext()) {
                _Connection _con = iter.next();
                if (_con.isClosed()) {
                    iter.remove();
                }
                else if (!_con.isInUse()) {
                    _con.setInUse(true);
                    conn = _con.getConnection();
                    break;
                }
            }
        }
        if (conn == null && timeout > 0) {
            try {
                Thread.sleep(timeout);
            }
            catch (InterruptedException e) {
            }
            conn = this.getFreeConnection(0);
            if (conn == null) {
                throw new OrmException("no have free connection!");
            }
        }
        return conn;
    }

    public void close() {
        this.closed = true;
        while(true) {
            synchronized (this.conns) {
                Iterator<_Connection> iter = this.conns.iterator();
                while (iter.hasNext()) {
                    try {
                        _Connection c = iter.next();
                        if(!c.isInUse()) {
                            c.close();
                            iter.remove();
                        }
                    }
                    catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
            if(this.conns.size() == 0) {
                break;
            }
            try {
                Thread.sleep(100);
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    public int getLoginTimeout() throws SQLException {
        return this.param.getLoginTimeout();
    }

    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    public void setLoginTimeout(int seconds) throws SQLException {

    }

    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    class DSMonitor extends Thread {
        _DataSource ds = null;

        public DSMonitor(_DataSource ds) {
            this.ds = ds;
            this.setName("DSMonitor");
            this.setDaemon(true);
        }

        @Override
        public void run() {
            while (!this.ds.closed) {
                synchronized (this.ds.conns) {
                    int size = this.ds.conns.size();
                    Mango.getLog().trace(this.ds.param.getConnName() + " Pool Size : " + size);
                    for (int i = size - 1; i > this.ds.param.getMinConnection(); i--) {
                        if (this.ds.conns.get(i).isInUse()) {
                            continue;
                        }
                        long last = this.ds.conns.get(i).getLastAccessTime();
                        long now = System.currentTimeMillis();
                        if (now - last >= this.ds.param.getReleaceTimeout() * 1000) {
                            _Connection conn = this.ds.conns.remove(i);
                            Mango.getLog().info("release connection " + conn + " : " + i);
                            i--;
                            try {
                                conn.close();
                            }
                            catch (SQLException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                    for (Iterator<_Connection> iter = this.ds.conns.iterator(); iter.hasNext();) {
                        _Connection conn = iter.next();
                        if (conn.isInUse()) {
                            continue;
                        }
                        if (conn.isClosed()) {
                            iter.remove();
                        }
                        else {
                            conn.reactive(this.ds.param.getTestSql());
                        }
                    }
                }
                try {
                    Thread.sleep(this.ds.param.getLoginTimeout() * 1000);
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}